from examples.feat_repeating_overlay.feat_repeating_overlay_model_classes import Trial, g
from vidigi.prep import reshape_for_animations, generate_animation_df
from vidigi.animation import generate_animation, add_repeating_overlay
from vidigi.utils import EventPosition, create_event_position_df
import os
import pandas as pd
import plotly.io as pio
pio.renderers.default = "notebook"Highlighting different periods with a repeating overlay
View Imported Code, which has had logging steps added at the appropriate points in the ‘model’ class
import random
import numpy as np
import pandas as pd
import simpy
from sim_tools.distributions import Exponential, Lognormal
from vidigi.resources import VidigiPriorityStore
# Class to store global parameter values. We don't create an instance of this
# class - we just refer to the class blueprint itself to access the numbers
# inside.
class g:
'''
Create a scenario to parameterise the simulation model
Parameters:
-----------
random_number_set: int, optional (default=DEFAULT_RNG_SET)
Set to control the initial seeds of each stream of pseudo
random numbers used in the model.
n_cubicles: int
The number of treatment cubicles
trauma_treat_mean: float
Mean of the trauma cubicle treatment distribution (Lognormal)
trauma_treat_var: float
Variance of the trauma cubicle treatment distribution (Lognormal)
arrival_rate: float
Set the mean of the exponential distribution that is used to sample the
inter-arrival time of patients
sim_duration: int
The number of time units the simulation will run for
number_of_runs: int
The number of times the simulation will be run with different random number streams
'''
random_number_set = 42
n_cubicles = 4
trauma_treat_mean = 40
trauma_treat_var = 5
unav_time = 60 * 12 # 12 hours
unav_freq = 60 * 12 # every 12 hours
arrival_rate = 5
sim_duration = 600
number_of_runs = 100
# Class representing patients coming in to the clinic.
class Patient:
'''
Class defining details for a patient entity
'''
def __init__(self, p_id):
'''
Constructor method
Params:
-----
identifier: int
a numeric identifier for the patient.
'''
self.identifier = p_id
self.arrival = -np.inf
self.wait_treat = -np.inf
self.total_time = -np.inf
self.treat_duration = -np.inf
# Class representing our model of the clinic.
class Model:
'''
Simulates the simplest minor treatment process for a patient
1. Arrive
2. Examined/treated by nurse when one available
3. Discharged
'''
# Constructor to set up the model for a run. We pass in a run number when
# we create a new model.
def __init__(self, run_number):
# Create a SimPy environment in which everything will live
self.env = simpy.Environment()
self.event_log = []
# Create a patient counter (which we'll use as a patient ID)
self.patient_counter = 0
self.patients = []
self.waiting_patients = []
# Create our resources
self.init_resources()
# Store the passed in run number
self.run_number = run_number
# Create an attribute to store the mean queuing times across this run of
# the model
self.mean_q_time_cubicle = 0
self.patient_inter_arrival_dist = Exponential(mean = g.arrival_rate,
random_seed = self.run_number*g.random_number_set)
self.treat_dist = Lognormal(mean = g.trauma_treat_mean,
stdev = g.trauma_treat_var,
random_seed = self.run_number*g.random_number_set)
def init_resources(self):
'''
Init the number of resources
and store in the arguments container object
Resource list:
1. Nurses/treatment bays (same thing in this model)
'''
self.treatment_cubicles = VidigiPriorityStore(self.env, num_resources=g.n_cubicles)
# A generator function that represents the DES generator for patient arrivals
def generator_patient_arrivals(self):
while True:
# Sample inter-arrival time
sampled_inter = self.patient_inter_arrival_dist.sample()
next_arrival_time = self.env.now + sampled_inter
# Calculate time until next closure and reopening
time_since_last_closure = self.env.now % g.unav_freq
time_until_closing = g.unav_freq - time_since_last_closure
unav_start = self.env.now + time_until_closing
# Allow people to start arriving again before the clinic opens
unav_end = unav_start + g.unav_time
# If the next patient would arrive during the closure period, skip forward
if next_arrival_time >= unav_start and next_arrival_time < unav_end:
yield self.env.timeout(unav_end - self.env.now)
continue # Restart loop after skipping closure period
# Wait for inter-arrival time before generating patient
yield self.env.timeout(sampled_inter)
self.patient_counter += 1
p = Patient(self.patient_counter)
self.patients.append(p)
self.env.process(self.attend_clinic(p))
# A generator function that represents the pathway for a patient going
# through the clinic.
# The patient object is passed in to the generator function so we can
# extract information from / record information to it
def attend_clinic(self, patient):
self.arrival = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Simplest',
'event_type': 'arrival_departure',
'event': 'arrival',
'time': self.env.now}
)
# request examination resource
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Simplest',
'event': 'treatment_wait_begins',
'event_type': 'queue',
'time': self.env.now}
)
# Seize a treatment resource when available
current_time = self.env.now
time_since_last_closure = current_time % g.unav_freq
time_until_closing = g.unav_freq - time_since_last_closure
treatment_request_event = self.treatment_cubicles.get(priority=1)
# Patients will give up and leave up to an hour before the clinic closes if they think they're
# not going to get seen in time.
result_of_queue = yield treatment_request_event | self.env.timeout(time_until_closing - min([random.randint(0, 60), time_until_closing]))
if treatment_request_event in result_of_queue:
cubicle = result_of_queue[treatment_request_event]
# record the waiting time
self.wait_treat = self.env.now - start_wait
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Simplest',
'event': 'treatment_begins',
'event_type': 'resource_use',
'time': self.env.now,
'resource_id': cubicle.id_attribute
}
)
# sample treatment duration
self.treat_duration = self.treat_dist.sample()
yield self.env.timeout(self.treat_duration)
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Simplest',
'event': 'treatment_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': cubicle.id_attribute}
)
self.treatment_cubicles.return_item(cubicle)
else:
self.treatment_cubicles.cancel_get(treatment_request_event)
# total time in system
self.total_time = self.env.now - self.arrival
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Simplest',
'event': 'depart',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# The run method starts up the DES entity generators, runs the simulation,
# and in turns calls anything we need to generate results for the run
def run(self):
# Start up our DES entity generators that create new patients. We've
# only got one in this model, but we'd need to do this for each one if
# we had multiple generators.
self.env.process(self.generator_patient_arrivals())
# Run the model for the duration specified in g class
self.env.run(until=g.sim_duration)
self.event_log = pd.DataFrame(self.event_log)
self.event_log["run"] = self.run_number
return self.event_log
# Class representing a Trial for our simulation - a batch of simulation runs.
class Trial:
# The constructor sets up a pandas dataframe that will store the key
# results from each run against run number, with run number as the index.
def __init__(self):
self.all_event_logs = []
# Method to run a trial
def run_trial(self):
print(f"{g.n_cubicles} nurses")
print("") ## Print a blank line
# Run the simulation for the number of runs specified in g class.
# For each run, we create a new instance of the Model class and call its
# run method, which sets everything else in motion. Once the run has
# completed, we grab out the stored run results (just mean queuing time
# here) and store it against the run number in the trial results
# dataframe.
for run in range(g.number_of_runs):
random.seed(run)
my_model = Model(run)
event_log = my_model.run()
self.all_event_logs.append(event_log)
self.all_event_logs = pd.concat(self.all_event_logs)g.sim_duration = 60 * 24 * 7 # 7 days
g.number_of_runs = 1
my_trial = Trial()
my_trial.run_trial()4 nurses
my_trial.all_event_logs.head(50)| patient | pathway | event_type | event | time | resource_id | run | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | Simplest | arrival_departure | arrival | 3.399660 | NaN | 0 |
| 1 | 1 | Simplest | queue | treatment_wait_begins | 3.399660 | NaN | 0 |
| 2 | 1 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0 |
| 3 | 2 | Simplest | arrival_departure | arrival | 8.497645 | NaN | 0 |
| 4 | 2 | Simplest | queue | treatment_wait_begins | 8.497645 | NaN | 0 |
| 5 | 2 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0 |
| 6 | 3 | Simplest | arrival_departure | arrival | 8.596678 | NaN | 0 |
| 7 | 3 | Simplest | queue | treatment_wait_begins | 8.596678 | NaN | 0 |
| 8 | 3 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0 |
| 9 | 4 | Simplest | arrival_departure | arrival | 8.608025 | NaN | 0 |
| 10 | 4 | Simplest | queue | treatment_wait_begins | 8.608025 | NaN | 0 |
| 11 | 4 | Simplest | resource_use | treatment_begins | 8.608025 | 4.0 | 0 |
| 12 | 5 | Simplest | arrival_departure | arrival | 11.359739 | NaN | 0 |
| 13 | 5 | Simplest | queue | treatment_wait_begins | 11.359739 | NaN | 0 |
| 14 | 6 | Simplest | arrival_departure | arrival | 19.509442 | NaN | 0 |
| 15 | 6 | Simplest | queue | treatment_wait_begins | 19.509442 | NaN | 0 |
| 16 | 7 | Simplest | arrival_departure | arrival | 22.877356 | NaN | 0 |
| 17 | 7 | Simplest | queue | treatment_wait_begins | 22.877356 | NaN | 0 |
| 18 | 8 | Simplest | arrival_departure | arrival | 26.653863 | NaN | 0 |
| 19 | 8 | Simplest | queue | treatment_wait_begins | 26.653863 | NaN | 0 |
| 20 | 9 | Simplest | arrival_departure | arrival | 40.737793 | NaN | 0 |
| 21 | 9 | Simplest | queue | treatment_wait_begins | 40.737793 | NaN | 0 |
| 22 | 1 | Simplest | resource_use_end | treatment_complete | 43.717044 | 1.0 | 0 |
| 23 | 1 | Simplest | arrival_departure | depart | 43.717044 | NaN | 0 |
| 24 | 5 | Simplest | resource_use | treatment_begins | 43.717044 | 1.0 | 0 |
| 25 | 2 | Simplest | resource_use_end | treatment_complete | 47.541216 | 2.0 | 0 |
| 26 | 2 | Simplest | arrival_departure | depart | 47.541216 | NaN | 0 |
| 27 | 6 | Simplest | resource_use | treatment_begins | 47.541216 | 2.0 | 0 |
| 28 | 4 | Simplest | resource_use_end | treatment_complete | 48.820975 | 4.0 | 0 |
| 29 | 4 | Simplest | arrival_departure | depart | 48.820975 | NaN | 0 |
| 30 | 7 | Simplest | resource_use | treatment_begins | 48.820975 | 4.0 | 0 |
| 31 | 3 | Simplest | resource_use_end | treatment_complete | 51.582490 | 3.0 | 0 |
| 32 | 3 | Simplest | arrival_departure | depart | 51.582490 | NaN | 0 |
| 33 | 8 | Simplest | resource_use | treatment_begins | 51.582490 | 3.0 | 0 |
| 34 | 10 | Simplest | arrival_departure | arrival | 71.026558 | NaN | 0 |
| 35 | 10 | Simplest | queue | treatment_wait_begins | 71.026558 | NaN | 0 |
| 36 | 5 | Simplest | resource_use_end | treatment_complete | 80.847148 | 1.0 | 0 |
| 37 | 5 | Simplest | arrival_departure | depart | 80.847148 | NaN | 0 |
| 38 | 9 | Simplest | resource_use | treatment_begins | 80.847148 | 1.0 | 0 |
| 39 | 11 | Simplest | arrival_departure | arrival | 87.458700 | NaN | 0 |
| 40 | 11 | Simplest | queue | treatment_wait_begins | 87.458700 | NaN | 0 |
| 41 | 12 | Simplest | arrival_departure | arrival | 87.465138 | NaN | 0 |
| 42 | 12 | Simplest | queue | treatment_wait_begins | 87.465138 | NaN | 0 |
| 43 | 6 | Simplest | resource_use_end | treatment_complete | 89.060237 | 2.0 | 0 |
| 44 | 6 | Simplest | arrival_departure | depart | 89.060237 | NaN | 0 |
| 45 | 10 | Simplest | resource_use | treatment_begins | 89.060237 | 2.0 | 0 |
| 46 | 7 | Simplest | resource_use_end | treatment_complete | 95.509386 | 4.0 | 0 |
| 47 | 7 | Simplest | arrival_departure | depart | 95.509386 | NaN | 0 |
| 48 | 11 | Simplest | resource_use | treatment_begins | 95.509386 | 4.0 | 0 |
| 49 | 8 | Simplest | resource_use_end | treatment_complete | 96.241403 | 3.0 | 0 |
# Create a list of EventPosition objects
event_position_df = create_event_position_df([
EventPosition(event='arrival', x=50, y=300, label="Arrival"),
EventPosition(event='treatment_wait_begins', x=205, y=275, label="Waiting for Treatment"),
EventPosition(event='treatment_begins', x=205, y=175, resource='n_cubicles', label="Being Treated"),
EventPosition(event='depart', x=270, y=70, label="Exit")
])my_trial.all_event_logs[my_trial.all_event_logs['run']==0]| patient | pathway | event_type | event | time | resource_id | run | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | Simplest | arrival_departure | arrival | 3.399660 | NaN | 0 |
| 1 | 1 | Simplest | queue | treatment_wait_begins | 3.399660 | NaN | 0 |
| 2 | 1 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0 |
| 3 | 2 | Simplest | arrival_departure | arrival | 8.497645 | NaN | 0 |
| 4 | 2 | Simplest | queue | treatment_wait_begins | 8.497645 | NaN | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 3981 | 914 | Simplest | arrival_departure | depart | 9374.860981 | NaN | 0 |
| 3982 | 915 | Simplest | resource_use_end | treatment_complete | 9377.867245 | 2.0 | 0 |
| 3983 | 915 | Simplest | arrival_departure | depart | 9377.867245 | NaN | 0 |
| 3984 | 912 | Simplest | resource_use_end | treatment_complete | 9381.404322 | 4.0 | 0 |
| 3985 | 912 | Simplest | arrival_departure | depart | 9381.404322 | NaN | 0 |
3986 rows × 7 columns
LIMIT_DURATION = g.sim_duration
WRAP_QUEUES_AT = 15
STEP_SNAPSHOT_MAX = WRAP_QUEUES_AT * 4
full_patient_df = reshape_for_animations(
event_log=my_trial.all_event_logs[my_trial.all_event_logs['run']==0],
every_x_time_units=10,
entity_col_name="patient",
step_snapshot_max=STEP_SNAPSHOT_MAX,
limit_duration=LIMIT_DURATION,
debug_mode=True
)
full_patient_df.head(15)Iteration through time-unit-by-time-unit logs complete 18:05:50
Snapshot df concatenation complete at 18:05:50
| snapshot_time | index | patient | pathway | event_type | event | time | resource_id | run | rank | additional | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 10 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN |
| 2 | 10 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN |
| 3 | 10 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN |
| 4 | 10 | 11.0 | 4.0 | Simplest | resource_use | treatment_begins | 8.608025 | 4.0 | 0.0 | 4.0 | NaN |
| 5 | 20 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN |
| 6 | 20 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN |
| 7 | 20 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN |
| 8 | 20 | 11.0 | 4.0 | Simplest | resource_use | treatment_begins | 8.608025 | 4.0 | 0.0 | 4.0 | NaN |
| 9 | 20 | 13.0 | 5.0 | Simplest | queue | treatment_wait_begins | 11.359739 | NaN | 0.0 | 1.0 | NaN |
| 10 | 20 | 15.0 | 6.0 | Simplest | queue | treatment_wait_begins | 19.509442 | NaN | 0.0 | 2.0 | NaN |
| 11 | 30 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN |
| 12 | 30 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN |
| 13 | 30 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN |
| 14 | 30 | 11.0 | 4.0 | Simplest | resource_use | treatment_begins | 8.608025 | 4.0 | 0.0 | 4.0 | NaN |
full_patient_df_plus_pos = generate_animation_df(
full_entity_df=full_patient_df,
event_position_df=event_position_df,
entity_col_name="patient",
wrap_queues_at=WRAP_QUEUES_AT,
step_snapshot_max=STEP_SNAPSHOT_MAX,
gap_between_entities=10,
gap_between_resources=10,
gap_between_resource_rows=30,
gap_between_queue_rows=30,
debug_mode=True
)
full_patient_df_plus_pos.sort_values(['patient', 'snapshot_time']).head(15)Placement dataframe finished construction at 18:05:50
| snapshot_time | index | patient | pathway | event_type | event | time | resource_id | run | rank | additional | x | y_final | label | resource | x_final | row | y | icon | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17207 | 10 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 205.0 | 0.0 | NaN | 🧔🏼 |
| 17208 | 20 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 205.0 | 0.0 | NaN | 🧔🏼 |
| 17209 | 30 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 205.0 | 0.0 | NaN | 🧔🏼 |
| 17210 | 40 | 2.0 | 1.0 | Simplest | resource_use | treatment_begins | 3.399660 | 1.0 | 0.0 | 1.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 205.0 | 0.0 | NaN | 🧔🏼 |
| 17206 | 50 | 2.0 | 1.0 | Simplest | resource_use | depart | 3.399660 | 1.0 | 0.0 | 1.0 | NaN | 270.0 | 70.0 | Exit | None | 270.0 | 0.0 | NaN | 🧔🏼 |
| 17212 | 10 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 195.0 | 0.0 | NaN | 👨🏿🦯 |
| 17213 | 20 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 195.0 | 0.0 | NaN | 👨🏿🦯 |
| 17214 | 30 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 195.0 | 0.0 | NaN | 👨🏿🦯 |
| 17215 | 40 | 5.0 | 2.0 | Simplest | resource_use | treatment_begins | 8.497645 | 2.0 | 0.0 | 2.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 195.0 | 0.0 | NaN | 👨🏿🦯 |
| 17211 | 50 | 5.0 | 2.0 | Simplest | resource_use | depart | 8.497645 | 2.0 | 0.0 | 2.0 | NaN | 270.0 | 70.0 | Exit | None | 260.0 | 0.0 | NaN | 👨🏿🦯 |
| 17222 | 10 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 185.0 | 0.0 | NaN | 👨🏻🦰 |
| 17223 | 20 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 185.0 | 0.0 | NaN | 👨🏻🦰 |
| 17224 | 30 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 185.0 | 0.0 | NaN | 👨🏻🦰 |
| 17225 | 40 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 3.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 185.0 | 0.0 | NaN | 👨🏻🦰 |
| 17226 | 50 | 8.0 | 3.0 | Simplest | resource_use | treatment_begins | 8.596678 | 3.0 | 0.0 | 1.0 | NaN | 205.0 | 175.0 | Being Treated | n_cubicles | 185.0 | 0.0 | NaN | 👨🏻🦰 |
full_patient_df_plus_pos[full_patient_df_plus_pos["patient"]=="overnight_closure"]| snapshot_time | index | patient | pathway | event_type | event | time | resource_id | run | rank | additional | x | y_final | label | resource | x_final | row | y | icon |
|---|
fig = generate_animation(
full_entity_df_plus_pos=full_patient_df_plus_pos.sort_values(['patient', 'snapshot_time']),
event_position_df= event_position_df,
scenario=g(),
entity_col_name="patient",
debug_mode=True,
setup_mode=False,
include_play_button=True,
start_time="08:00:00",
entity_icon_size=20,
resource_icon_size=20,
gap_between_resource_rows=30,
plotly_height=700,
frame_duration=800,
frame_transition_duration=200,
plotly_width=1200,
override_x_max=300,
override_y_max=500,
time_display_units="day_clock_ampm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_1_simplest_case/Simplest%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
)
figOutput animation generation complete at 18:10:46
Adding the overlay
If you drag the slider through to 8pm, you will see an overlay showing the clinic is closed. This will continue through until 8am the next day.
add_repeating_overlay(
fig,
"🌙 Clinic Closed",
first_start_frame=int((60 * 12) / 10), # After 12 hours, but we only have a frame every 10 minutes
on_duration_frames=int((60 * 12) / 10),
off_duration_frames=int((60 * 12) / 10),
rect_color="navy",
rect_opacity=0.1
)Overlay in a different position with no overlay
fig = generate_animation(
full_entity_df_plus_pos=full_patient_df_plus_pos.sort_values(['patient', 'snapshot_time']),
event_position_df= event_position_df,
scenario=g(),
entity_col_name="patient",
debug_mode=True,
setup_mode=False,
include_play_button=True,
start_time="08:00:00",
entity_icon_size=20,
resource_icon_size=20,
gap_between_resource_rows=30,
plotly_height=700,
frame_duration=800,
frame_transition_duration=200,
plotly_width=1200,
override_x_max=300,
override_y_max=500,
time_display_units="day_clock_ampm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_1_simplest_case/Simplest%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
)
figOutput animation generation complete at 18:11:07
add_repeating_overlay(
fig,
"🌙<br>Clinic<br>Closed",
first_start_frame=int((60 * 12) / 10), # After 12 hours, but we only have a frame every 10 minutes
on_duration_frames=int((60 * 12) / 10),
off_duration_frames=int((60 * 12) / 10),
rect_opacity=0,
relative_text_position_x=0.85,
relative_text_position_y=0.7,
text_font_color="black"
)